package com.googlecode.rotools.rocommon;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;


public class IOUtil {
	private static class Resource
	{
		private ClassLoader classLoader;
		private String resourceName;
	}
	private static interface Input<T>
	{
		InputStream getInputStream(T source) throws IOException;
	}
	private static interface Output<T>
	{
		OutputStream getOutputStream(T source) throws IOException;
	}
	private static abstract class InOut<T> implements Input<T>, Output<T>
	{
	}
	
	private byte[] buffer;
	private Resource resource;
	private InOut<File> fileInOut;
	private Input<Resource> resourceInput;
	private Input<byte[]> bytesInput;
	
	private Resource getResource(String fullName){
		return getResource(getClass().getClassLoader(), fullName);
	}
	private Resource getResource(Class<?> cls, String name){
		String packageName = cls.getPackage().getName();
		return getResource(
				cls.getClassLoader(),
				packageName.replace('.', '/') + "/" + name
				); 
	}
	private Resource getResource(ClassLoader classLoader, String fullName){
		if(resource == null){
			resource = new Resource();
		}
		resource.classLoader = classLoader;
		resource.resourceName = fullName;
		return resource;
	}
	private InOut<File> getFileInOut(){
		if(fileInOut == null){
			fileInOut = new InOut<File>(){
				public InputStream getInputStream(File source) throws IOException {
					return new FileInputStream(source);
				}
				public OutputStream getOutputStream(File source) throws IOException {
					return new FileOutputStream(source);
				}
			};
		}
		return fileInOut;
	}
	private Input<Resource> getResourceInput(){
		if(resourceInput == null){
			resourceInput = new Input<Resource>(){
				public InputStream getInputStream(Resource source) throws IOException {
					return source.classLoader.getResourceAsStream(source.resourceName);
				}
			};
		}
		return resourceInput;
	}
	private Input<byte[]> getBytesInput(){
		if(bytesInput == null){
			bytesInput = new Input<byte[]>() {
				public InputStream getInputStream(byte[] source) throws IOException {
					return new ByteArrayInputStream(source);
				}
			};
		}
		return bytesInput;
	}
	public IOUtil() {
		this(4096);
	}
	public IOUtil(int bufferSize) {
		super();
		this.buffer = new byte[bufferSize];
	}
	public void copy(byte[] src, File dist) throws IOException{
		copy(getBytesInput(), src, getFileInOut(), dist);
	}
	public byte[] readBytes(String fullName) throws IOException{
		return readBytes(getResource(fullName));
	}
	public byte[] readBytes(ClassLoader classLoader, String fullName) throws IOException{
		return readBytes(getResource(classLoader, fullName));
	}
	public byte[] readBytes(Class<?> cls, String name) throws IOException{
		return readBytes(getResource(cls, name));
	}
	private byte[] readBytes(Resource resource) throws IOException{
		return readBytes(getResourceInput(), resource);
	}
	public byte[] readBytes(File file) throws IOException{
		return readBytes(getFileInOut(), file);
	}
	private <T> byte[] readBytes(Input<T> input, T source) throws IOException{
		InputStream in = input.getInputStream(source);
		try{
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			try{
				for (int len = 0; -1 != (len = in.read(buffer)); ) {
					out.write(buffer, 0, len);
				}
				return out.toByteArray();
				
			}finally{
				out.close();
			}
		}finally{
			in.close();
		}
	}
	public Properties readProperties(String fullName) throws IOException{
		return readProperties(getResource(fullName));
	}
	public Properties readProperties(ClassLoader classLoader, String fullName) throws IOException{
		return readProperties(getResource(classLoader, fullName));
	}
	public Properties readProperties(Class<?> cls, String name) throws IOException{
		return readProperties(getResource(cls, name));
	}
	private Properties readProperties(Resource resource) throws IOException{
		return readProperties(getResourceInput(), resource);
	}
	public Properties readProperties(File file) throws IOException{
		return readProperties(getFileInOut(), file);
	}
	private <T> Properties readProperties(Input<T> input, T source) throws IOException{
		Properties properties = new Properties();
		copy(input, source, properties);
		return properties;
	}
	private <T> void copy(Input<T> input, T source, Properties dist) throws IOException{
		InputStream in = input.getInputStream(source);
		try{
			dist.load(in);
			
		}finally{
			in.close();
			
		}
	}
	private <S, O> void copy(Input<S> input, S source, Output<O> output, O dist) throws IOException{
		InputStream in = input.getInputStream(source);
		try{
			OutputStream out = output.getOutputStream(dist);
			try{
				int len = 0;
				while(-1 != (len = in.read(buffer))){
					out.write(buffer, 0, len);
				}
				
			}finally{
				out.close();
			}
			
		}finally{
			in.close();
			
		}
	}
}
